package com.zhaolaobao.redis.queue.config;

import com.zhaolaobao.redis.queue.annotation.RedisDelayQueueHandler;
import com.zhaolaobao.redis.queue.annotation.RedisDelayQueueListener;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName RedisDelayQueueBeanProcessor
 * @Description //
 * @Author WHX
 * @Date 2021/10/29 11:15
 **/
@Component
public class RedisDelayQueueBeanProcessor implements BeanPostProcessor {

    protected static Map<String, MethodQuote> methodCache = new ConcurrentHashMap<>();

    /**
     * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
     * instance and the objects created by the FactoryBean (as of Spring 2.0). The
     * post-processor can decide whether to apply to either the FactoryBean or created
     * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
     * <p>This callback will also be invoked after a short-circuiting triggered by a
     * in contrast to all other BeanPostProcessor callbacks.
     * <p>The default implementation returns the given {@code bean} as-is.
     *
     * @param bean     the new bean instance
     * @param beanName the name of the bean
     * @return the bean instance to use, either the original or a wrapped one;
     * if {@code null}, no subsequent BeanPostProcessors will be invoked
     * @throws BeansException in case of errors
     * @see InitializingBean#afterPropertiesSet
     * @see FactoryBean
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (null == bean) return bean;
        if (bean.getClass().isAnnotationPresent(RedisDelayQueueHandler.class)) {
            Method[] methods = bean.getClass().getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(RedisDelayQueueListener.class)) {
                    // add catch
                    RedisDelayQueueListener annotation = method.getAnnotation(RedisDelayQueueListener.class);
                    String queueName = annotation.queueName();
                    if (!StringUtils.hasText(queueName))
                        throw new RuntimeException("队列名不能为空：" + bean.getClass().getTypeName() + "#" + method.getName());
                    MethodQuote m = methodCache.get(queueName);
                    if (m != null) {
                        String oldMethod = m.getMethod().getDeclaringClass().getTypeName() + "#" + m.getMethod().getName();
                        String newMethod = bean.getClass().getTypeName() + "#" + method.getName();
                        if (!Objects.equals(oldMethod, newMethod)) {
                            throw new RuntimeException("队列名重复：" + queueName + "\n"
                                    + oldMethod + "\n" + newMethod);
                        }
                    }
                    methodCache.put(queueName, new MethodQuote(bean, method));
                }
            }
        }
        return bean;
    }
}
